package com.sharer.last.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.yulichang.base.MPJBaseServiceImpl;
import com.sharer.last.base.enums.BooleanEnum;
import com.sharer.last.base.enums.BusinessExceptionCode;
import com.sharer.last.base.enums.ExceptionCode;
import com.sharer.last.base.enums.ExceptionPrefixParamEnum;
import com.sharer.last.base.exception.BusinessException;
import com.sharer.last.base.model.PageVO;
import com.sharer.last.base.support.QueryCondition;
import com.sharer.last.base.utils.CollectionUtil;
import com.sharer.last.base.utils.ConvertUtil;
import com.sharer.last.base.utils.StringUtil;
import com.sharer.last.enums.BlogShowPermissionEnum;
import com.sharer.last.mapper.BlogMapper;
import com.sharer.last.model.bo.BlogArchiveBO;
import com.sharer.last.model.bo.BlogIdAndPwdBO;
import com.sharer.last.model.dto.blog.*;
import com.sharer.last.model.po.BlogPO;
import com.sharer.last.model.po.CategoryPO;
import com.sharer.last.model.po.TagPO;
import com.sharer.last.model.vo.blog.*;
import com.sharer.last.model.vo.category.CategoryAndTagVO;
import com.sharer.last.service.*;
import com.sharer.last.utils.BeanUtil;
import com.sharer.last.utils.JwtUtils;
import com.sharer.last.utils.json.JsonUtil;
import com.sharer.last.wrapper.BlogWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 博客文章业务层实现
 *
 * @ClassName BlogServiceImpl
 * @Author wangjin
 * @Date 2023/5/5 20:43
 * @Description
 * @Version 1.0
 */
@Service
public class BlogServiceImpl extends MPJBaseServiceImpl<BlogMapper, BlogPO> implements BlogService {
    @Resource
    BlogMapper blogMapper;

    @Resource
    BlogWrapper blogWrapper;

    @Resource
    CategoryService categoryService;

    @Resource
    CommentService commentService;

    @Resource
    BlogTagService blogTagService;

    @Resource
    TagService tagService;

    @Resource
    RedisService redisService;

    /**
     * 查询条件构造
     *
     * @param queryDTO 查询参数封装
     * @return
     */
    private LambdaQueryWrapper<BlogPO> buildQueryWrapper(BlogQueryDTO queryDTO) {
        LambdaQueryWrapper<BlogPO> qw = new LambdaQueryWrapper<>();
        // 类别id
        Integer categoryId = queryDTO.getCategoryId();
        if (ConvertUtil.toInt(categoryId) > 0) {
            qw.eq(BlogPO::getCategoryId, categoryId);
        }
        // 标题
        String title = queryDTO.getTitle();
        if (StringUtil.isNotBlank(title)) {
            qw.like(BlogPO::getTitle, title);
        }
        // 内容
        String content = queryDTO.getContent();
        if (StringUtil.isNotBlank(content)) {
            qw.like(BlogPO::getContent, content);
        }
        // 标签
        Integer tagId = queryDTO.getTagId();
        if (ConvertUtil.toInt(tagId) > 0) {
            List<Long> blogIdList = blogTagService.getBlogIdsByTagId(tagId);
            blogIdList.add(-1L);
            qw.in(BlogPO::getId, blogIdList);
        }
        // 数据权限范围
        String  adminToken = queryDTO.getAdminToken();
        String access_token = (String) redisService.get("access_token");
        if (StringUtil.isBlank(adminToken) || !adminToken.equals(access_token)) {
            qw.in(BlogPO::getShowPermission, BlogShowPermissionEnum.getNotPrivacyCodeList());
        }
        // 关键词搜索
        String keyword = queryDTO.getKeyword();
        if (StringUtil.isNotBlank(keyword)) {
            qw.and(wrapper -> wrapper.like(BlogPO::getTitle, keyword).or().like(BlogPO::getContent, keyword).or().like(BlogPO::getDescription, keyword));
        }
        // 赞赏
        Integer isAppreciation = queryDTO.getIsAppreciation();
        if (ConvertUtil.toInt(isAppreciation) > 0) {
            qw.eq(BlogPO::getIsAppreciation, isAppreciation);
        }
        // 评论开关
        Integer isCommentEnabled = queryDTO.getIsCommentEnabled();
        if (ConvertUtil.toInt(isCommentEnabled) > 0) {
            qw.eq(BlogPO::getIsCommentEnabled, isCommentEnabled);
        }
        // 推荐
        Integer isRecommend = queryDTO.getIsRecommend();
        if (ConvertUtil.toInt(isRecommend) > 0) {
            qw.eq(BlogPO::getIsRecommend, isRecommend);
        }
        // 置顶
        Integer isTop = queryDTO.getIsTop();
        if (ConvertUtil.toInt(isTop) > 0) {
            qw.eq(BlogPO::getIsTop, isTop);
        }
        qw.orderByDesc(BlogPO::getCreateTime);
        return qw;
    }

    /**
     * 统计公开博客总数
     *
     * @return
     */
    @Override
    public long countBlogByIsPrivacy(String adminToken) {
        LambdaQueryWrapper<BlogPO> qw = new LambdaQueryWrapper<>();
        String access_token = (String) redisService.get("access_token");
        if (StringUtil.isBlank(adminToken) || !adminToken.equals(access_token)) {
            qw.in(BlogPO::getShowPermission, BlogShowPermissionEnum.getNotPrivacyCodeList());
        }
        return this.count(qw);
    }

    /**
     * 校验博客
     *
     * @param relevantId
     * @param token
     */
    @Override
    public void checkBlog(Long relevantId, String token) {
        if (ConvertUtil.toInt(relevantId) < 1) {
            throw new BusinessException(BusinessExceptionCode.ID_IS_NULL, ExceptionPrefixParamEnum.BLOG);
        }
        BlogPO blog = this.getById(relevantId);
        if (blog == null) {
            throw new BusinessException(BusinessExceptionCode.DATA_IS_EMPTY, ExceptionPrefixParamEnum.BLOG);
        }
        Integer showPermission = blog.getShowPermission();
        if (showPermission == 3) {
            String subject = JwtUtils.getTokenBody(token).getSubject();
            //经密码验证后的Token
            BlogIdAndPwdBO bo = JsonUtil.parse(subject, BlogIdAndPwdBO.class);
            if (bo == null) {
                throw new BusinessException(BusinessExceptionCode.CHECK_TOKEN_FAIL, ExceptionPrefixParamEnum.BLOG);
            }
            if (!bo.getId().equals(blog.getId()) || !bo.getPassword().equals(blog.getPassword())) {
                throw new BusinessException(BusinessExceptionCode.CHECK_TOKEN_FAIL, ExceptionPrefixParamEnum.BLOG);
            }
        }
    }

    /**
     * 校验受保护文章密码并返回密码生成的token
     *
     * @param dto
     * @return
     */
    @Override
    public BlogJwtVO checkBlogPassword(BlogPasswordDTO dto) {
        Long   blogId   = dto.getBlogId();
        String password = dto.getPassword();
        BlogPO blogPO   = this.getById(blogId);
        if (blogPO == null) {
            throw new BusinessException(BusinessExceptionCode.DATA_IS_EMPTY, ExceptionPrefixParamEnum.BLOG);
        }
        if (blogPO.getShowPermission() != 3) {
            throw new BusinessException(BusinessExceptionCode.BLOG_SHOW_PERMISSION, ExceptionPrefixParamEnum.BLOG);
        }
        if (!blogPO.getPassword().equals(password)) {
            throw new BusinessException(ExceptionCode.PASSWORD_IS_ERROR);
        }
        //生成有效时间一个月的Token
        BlogIdAndPwdBO blogIdAndPwdBO = BlogIdAndPwdBO.builder().id(blogId).password(password).build();
        String         jwt            = JwtUtils.generateToken(JsonUtil.toJson(blogIdAndPwdBO), 1000 * 3600 * 24 * 30L);
        return BlogJwtVO.builder().id(blogId).jwt(jwt).build();
    }

    /**
     * 校验密码正确性
     *
     * @param dto
     * @return
     */
    @Override
    public Boolean validateBlogPasswordCorrect(BlogValidateDTO dto) {
        Long   id    = dto.getId();
        String token = dto.getToken();
        BlogPO blog  = this.getById(id);
        if (blog == null) {
            return false;
        }
        if (blog.getShowPermission() != 3) {
            return true;
        }
        if (StringUtil.isBlank(token)) {
            return false;
        }
        String subject = JwtUtils.getTokenBody(token).getSubject();
        //经密码验证后的Token
        BlogIdAndPwdBO bo = JsonUtil.parse(subject, BlogIdAndPwdBO.class);
        if (bo == null) {
            return false;
        }
        return bo.getId().equals(blog.getId()) && bo.getPassword().equals(blog.getPassword());
    }

    /**
     * 获取指定分类下的博客数量
     *
     * @param categoryId
     * @return
     */
    @Override
    public long getBlogNumByCategoryId(Long categoryId) {
        LambdaQueryWrapper<BlogPO> qw = new LambdaQueryWrapper<>();
        qw.eq(BlogPO::getCategoryId, categoryId);
        return this.count(qw);
    }

    /**
     * 获取博客关联数据
     * 分类 标签
     *
     * @return
     */
    @Override
    public CategoryAndTagVO findBlogRelatedData() {
        List<TagPO>      tagPOList   = tagService.list();
        List<CategoryPO> categoryPOS = categoryService.list();
        return CategoryAndTagVO.builder().categoryList(categoryPOS).tagList(tagPOList).build();
    }

    /**
     * <h3>根据id查询数据</h3>
     * <p>每调用一次此方法相当于查看一次博客</p>
     *
     * @param id 主键id
     * @return
     */
    @Override
    public BlogVO findById(Long id, Boolean throwException) {
        BlogPO blog = this.getById(id);
        if (blog == null) {
            if (throwException) {
                throw new BusinessException(BusinessExceptionCode.DATA_IS_EMPTY, ExceptionPrefixParamEnum.BLOG);
            } else {
                return null;
            }
        }
        return blogWrapper.entityVO(blog);
    }

    /**
     * 根据id查询数据
     *
     * @param id 博客id
     * @return
     */
    @Override
    public NewBlogVO findById(Long id) {
        BlogPO blog = this.getById(id);
        if (blog == null) {
            throw new BusinessException(BusinessExceptionCode.DATA_IS_EMPTY, ExceptionPrefixParamEnum.BLOG);
        }
        return BeanUtil.copy(blog, NewBlogVO.class);
    }

    /**
     * 根据token获取博客数据
     *
     * @param id
     * @param token 博客密码生成的token
     * @return
     */
    @Override
    public BlogVO findBlogByToken(Long id, String token) {
        BlogVO blog = this.findById(id, true);
        if (blog.getShowPermission() == 3) {
            if (StringUtil.isBlank(token)) {
                throw new BusinessException(ExceptionCode.TOKEN_IS_EMPTY, ExceptionPrefixParamEnum.BLOG);
            }
            String subject = JwtUtils.getTokenBody(token).getSubject();
            //经密码验证后的Token
            BlogIdAndPwdBO bo = JsonUtil.parse(subject, BlogIdAndPwdBO.class);
            if (bo == null) {
                throw new BusinessException(BusinessExceptionCode.CHECK_TOKEN_FAIL, ExceptionPrefixParamEnum.BLOG);
            }
            if (!bo.getId().equals(blog.getId()) || !bo.getPassword().equals(blog.getPassword())) {
                throw new BusinessException(BusinessExceptionCode.CHECK_TOKEN_FAIL, ExceptionPrefixParamEnum.BLOG);
            }
        }
        return blog;
    }

    /**
     * 条件查询
     *
     * @param queryDTO
     * @return
     */
    @Override
    public List<BlogPO> queryList(BlogQueryDTO queryDTO) {
        LambdaQueryWrapper<BlogPO> qw = this.buildQueryWrapper(queryDTO);
        return this.list(qw);
    }

    /**
     * 条件查询
     *
     * @param queryDTO
     * @return
     */
    @Override
    public List<BlogVO> selectList(BlogQueryDTO queryDTO) {
        List<BlogPO> list = this.queryList(queryDTO);
        return blogWrapper.listVO(list);
    }

    /**
     * 分页查询
     *
     * @param queryDTO
     * @return
     */
    @Override
    public PageVO<BlogVO> selectListByPage(BlogQueryDTO queryDTO) {
        LambdaQueryWrapper<BlogPO> qw   = this.buildQueryWrapper(queryDTO);
        IPage<BlogPO>              page = this.page(QueryCondition.getPage(queryDTO.getPage()), qw);
        return blogWrapper.toPageVO(page);
    }

    /**
     * 获取最新推荐博客 5条
     *
     * @return
     */
    @Override
    public List<NewBlogVO> selectNewestRecommendBlogList(String adminToken) {
        BlogQueryDTO               queryDTO = BlogQueryDTO.builder().adminToken(adminToken).isRecommend(BooleanEnum.TRUE.getCode()).build();
        LambdaQueryWrapper<BlogPO> qw       = this.buildQueryWrapper(queryDTO);
        qw.last("limit 5");
        List<BlogPO> list = this.list(qw);
        return list.stream().map(m -> BeanUtil.copy(m, NewBlogVO.class)).collect(Collectors.toList());
    }

    /**
     * 获取随机博客 5条 已公开
     *
     * @return
     */
    @Override
    public List<RandomBlogVO> selectRandomBlogList(String adminToken) {
        BlogQueryDTO               queryDTO = BlogQueryDTO.builder().adminToken(adminToken).build();
        LambdaQueryWrapper<BlogPO> qw       = this.buildQueryWrapper(queryDTO);
        qw.last("limit 5");
        List<BlogPO> list = this.list(qw);
        return list.stream().map(m -> BeanUtil.copy(m, RandomBlogVO.class)).collect(Collectors.toList());
    }

    /**
     * 获取所有博客 只需要id和博客标题
     * 后台专用
     *
     * @return
     */
    @Override
    public List<BlogIdAndTitleVO> selectSimpleList() {
        List<BlogPO>           list = this.list();
        List<BlogIdAndTitleVO> res  = list.stream().map(m -> BeanUtil.copy(m, BlogIdAndTitleVO.class)).collect(Collectors.toList());
        res.add(BlogIdAndTitleVO.builder().id(-2L).title("友人帐").build());
        res.add(BlogIdAndTitleVO.builder().id(-1L).title("关于我").build());
        return res;
    }

    /**
     * 获取所有博客 只需要id和博客标题
     *
     * @param blogIdList 博客文章id集合
     * @return
     */
    @Override
    public Map<Long, BlogIdAndTitleVO> selectBlogTitleAndIdList(List<Long> blogIdList) {
        Map<Long, BlogIdAndTitleVO> map = new HashMap<>();
        if (CollectionUtil.isNotEmpty(blogIdList)) {
            LambdaQueryWrapper<BlogPO> qw = new LambdaQueryWrapper<>();
            qw.in(BlogPO::getId, blogIdList);
            List<BlogIdAndTitleVO> collect = this.list(qw).stream().map(m -> BeanUtil.copy(m, BlogIdAndTitleVO.class)).collect(Collectors.toList());
            for (BlogIdAndTitleVO blogIdAndTitleVO : collect) {
                map.put(blogIdAndTitleVO.getId(), blogIdAndTitleVO);
            }
        }
        return map;
    }

    /**
     * 根据年月分组查询博客数据
     *
     * @return
     */
    @Override
    public List<ArchiveBlogVO> selectBlogListGroupByYearMonth(String adminToken) {
        // 查询非隐私博客
        BlogQueryDTO               queryDTO = BlogQueryDTO.builder().adminToken(adminToken).build();
        LambdaQueryWrapper<BlogPO> qw       = this.buildQueryWrapper(queryDTO);
        List<BlogPO>               list     = this.list(qw);
        // 基于年月分组
        Map<String, List<BlogPO>> map = list.stream().collect(Collectors.groupingBy(item -> {
            LocalDateTime createTime = item.getCreateTime();
            int           year       = createTime.getYear();
            int           month      = createTime.getMonthValue();
            return year + "-" + month;
        }));
        // 封装 显示月份第多少天
        List<ArchiveBlogVO> archiveBlogList = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(map)) {
            map.forEach((k, v) -> {
                List<BlogArchiveBO> boList = new ArrayList<>();
                for (BlogPO blogPO : v) {
                    BlogArchiveBO blogArchiveBO = BeanUtil.copy(blogPO, BlogArchiveBO.class);
                    LocalDateTime createTime    = blogPO.getCreateTime();
                    if (createTime != null) {
                        blogArchiveBO.setDay(createTime.getDayOfMonth() + "日");
                    }
                    boList.add(blogArchiveBO);
                }
                archiveBlogList.add(ArchiveBlogVO.builder().archiveList(boList).year(k).build());
            });
        }
        return archiveBlogList;
    }

    /**
     * 关键词搜索博客
     *
     * @param queryDTO
     * @return
     */
    @Override
    public List<SearchBlogVO> selectBlogListBySearch(BlogQueryDTO queryDTO) {
        List<BlogPO>       list           = queryList(queryDTO);
        List<SearchBlogVO> searchBlogList = list.stream().map(m -> BeanUtil.copy(m, SearchBlogVO.class)).collect(Collectors.toList());
        for (SearchBlogVO searchBlog : searchBlogList) {
            String content       = searchBlog.getContent();
            int    contentLength = content.length();
            int    index         = content.indexOf(queryDTO.getKeyword()) - 10;
            index = index < 0 ? 0 : index;
            int end = index + 21;//以关键字字符串为中心返回21个字
            end = end > contentLength - 1 ? contentLength - 1 : end;
            searchBlog.setContent(content.substring(index, end));
        }
        return searchBlogList;
    }

    /**
     * 保存草稿或发布新文章
     *
     * @param dto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createBlog(BlogChangeDTO dto) {
        BlogPO blogPO = BeanUtil.copy(dto, BlogPO.class);
        // 校验分类 不存在则新建
        CategoryPO categoryPO = categoryService.createCategory(dto.getCategoryName());
        blogPO.setCategoryId(categoryPO.getId());
        // 新增博客
        if (!this.saveOrUpdate(blogPO)) {
            throw new BusinessException(BusinessExceptionCode.DATA_INSERT_FAIL, ExceptionPrefixParamEnum.BLOG);
        }
        // 校验标签 不存在则新建
        tagService.createTag(blogPO.getId(), dto.getTagList());
    }

    /**
     * 更新博客
     *
     * @param dto
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateBlog(BlogChangeDTO dto) {
        Long id = dto.getId();
        if (ConvertUtil.toInt(id) < 1) {
            throw new BusinessException(BusinessExceptionCode.ID_IS_NULL, ExceptionPrefixParamEnum.BLOG);
        }
        // 判断博客是否存在
        BlogPO blog = this.getById(id);
        if (blog == null) {
            throw new BusinessException(BusinessExceptionCode.DATA_IS_EMPTY, ExceptionPrefixParamEnum.BLOG);
        }
        BlogPO blogPO = BeanUtil.copy(dto, BlogPO.class);
        // 校验分类 不存在则新建
        CategoryPO categoryPO = categoryService.createCategory(dto.getCategoryName());
        blogPO.setCategoryId(categoryPO.getId());

        if (!this.updateById(blogPO)) {
            throw new BusinessException(BusinessExceptionCode.DATA_UPDATE_FAIL, ExceptionPrefixParamEnum.BLOG);
        }
        // 校验标签 不存在则新建
        tagService.createTag(blogPO.getId(), dto.getTagList());
    }

    /**
     * 更新博客置顶状态
     *
     * @param blogId 博客id
     * @param top
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateBlogTopById(Long blogId, Integer top) {
        // 查询数据是否存在
        BlogPO blog = this.getById(blogId);
        if (blog == null) {
            throw new BusinessException(BusinessExceptionCode.DATA_IS_EMPTY, ExceptionPrefixParamEnum.BLOG);
        }
        BlogPO blogPO = new BlogPO();
        blogPO.setId(blogId);
        blogPO.setIsTop(top);
        if (!this.updateById(blogPO)) {
            throw new BusinessException(BusinessExceptionCode.DATA_UPDATE_FAIL, ExceptionPrefixParamEnum.BLOG);
        }
    }

    /**
     * 更新博客推荐状态
     *
     * @param blogId
     * @param recommend
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateBlogRecommendById(Long blogId, Integer recommend) {
        // 查询数据是否存在
        BlogPO blog = this.getById(blogId);
        if (blog == null) {
            throw new BusinessException(BusinessExceptionCode.DATA_IS_EMPTY, ExceptionPrefixParamEnum.BLOG);
        }
        BlogPO blogPO = new BlogPO();
        blogPO.setId(blogId);
        blogPO.setIsRecommend(recommend);
        if (!this.updateById(blogPO)) {
            throw new BusinessException(BusinessExceptionCode.DATA_UPDATE_FAIL, ExceptionPrefixParamEnum.BLOG);
        }
    }

    /**
     * 更新博客可见性状态
     *
     * @param dto
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateBlogVisibilityById(BlogVisibilityDTO dto) {
        // 查询数据是否存在
        Long   id   = dto.getId();
        BlogPO blog = this.getById(id);
        if (blog == null) {
            throw new BusinessException(BusinessExceptionCode.DATA_IS_EMPTY, ExceptionPrefixParamEnum.BLOG);
        }
        BlogPO blogPO = BeanUtil.copy(dto, BlogPO.class);
        if (blogPO.getShowPermission() != 3) {
            blogPO.setPassword("");
        }
        if (!this.updateById(blogPO)) {
            throw new BusinessException(BusinessExceptionCode.DATA_UPDATE_FAIL, ExceptionPrefixParamEnum.BLOG);
        }
    }

    /**
     * 根据id删除博客
     *
     * @param blogId 博客
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeBlogById(Long blogId) {
        // 判断博客是否存在
        BlogPO blog = this.getById(blogId);
        if (blog == null) {
            throw new BusinessException(BusinessExceptionCode.DATA_IS_EMPTY, ExceptionPrefixParamEnum.BLOG);
        }
        // 删除博客标签关联关系
        boolean res = blogTagService.deleteByBlogId(blogId);
        if (!res) {
            throw new BusinessException(BusinessExceptionCode.DATA_REMOVE_FAIL, ExceptionPrefixParamEnum.BLOG_TAG);
        }
        // 删除博客
        res = this.removeById(blogId);
        if (!res) {
            throw new BusinessException(BusinessExceptionCode.DATA_REMOVE_FAIL, ExceptionPrefixParamEnum.BLOG);
        }
        // 删除评论
        commentService.removeByRelevantId(blogId);
    }
}
